/*
 * Created on 12-may-06
 *
 * To change the template for this generated file go to
 * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
 */
package org.jma.lib.soap;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;


import org.jma.lib.soap.events.SOAPSessionEvent;
import org.jma.lib.soap.sessions.SOAPSession;
import org.jma.lib.soap.sessions.SOAPSessionException;
import org.jma.lib.soap.sessions.SOAPSessionFilter;
import org.jma.lib.soap.sessions.SOAPSessionListener;
import org.jma.lib.soap.sessions.SOAPSessionWatchdog;
import org.jma.lib.soap.utils.XMLSerializable;
import org.jma.lib.utils.multithreading.Mutex;
import org.jma.lib.utils.multithreading.UniqueIdentifier;
import org.jma.lib.utils.strings.StringFormat;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;


/**
 * @author jesus
 *
 * To change the template for this generated type comment go to
 * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
 */
public class SOAPProxy  {
	public static final String SOAPSession = "JMA.SOAPSESSION";
	public static final String SOAPAplication = "JMA.APP";
	private SOAPApplication sa;
	


	private Map SOAPSessions= Collections.synchronizedMap(new HashMap());
	//This thread controls session timeout
	private SOAPSessionWatchdog sessionWatchDog = new SOAPSessionWatchdog(this);
	
	private ArrayList  SOAPSessionListeners=new ArrayList();
	private ArrayList  SOAPSessionFilters=new ArrayList();
	
	private Map MethodsCache= Collections.synchronizedMap(new HashMap());

	
	public static final String SOAP_SESSION_ID="${SOAP_SESSION_ID}";
	private  static final String SOAP_RESPONSE_TEMPLATE=
	"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" +	"<SOAP:Envelope xmlns:SOAP=\"http://schemas.xmlsoap.org/soap/envelope/\" " +	"xmlns:xsi=\"http://www.w3.org/1999/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/1999/XMLSchema\">" +
	"${header}"	+
	"<SOAP:Body>" +
	"<ns:${method}Response xmlns:ns=\"${className}\" SOAP:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" +
	"<return>" +	"${return}" +
	"</return>"+
	"</ns:${method}Response>" +
	"${fault}"+
	"</SOAP:Body>" +
	"</SOAP:Envelope>\r\n";
	
	
	
	
	private static final String SOAP_HEADER_RESPONSE_TEMPLATE=
	"<SOAP:Header>"+
	"<sessionId>" +
	"${idsession}"+
	"</sessionId>"+	
	"</SOAP:Header>";
	
	private static final String SOAP_FAULT_TEMPLATE=
		"<SOAP:Fault>"+
		"<faultcode>" +
		"${faultcode}"+
		"</faultcode>" +
		"<faultstring>" +
		"${faultstring}"+
		"</faultstring>" +
		"<faultactor>" +
		"${faultactor}"+
		"</faultactor>" +
		"</SOAP:Fault>";
	
	private static final String SOAP_WSDL_ANY = 
	"<s:element name=\"${method}Response\">" +
	"<s:complexType>" +
	"<s:sequence>" +
	"<s:element minOccurs=\"0\" maxOccurs=\"1\" name=\"${method}Result>\"" +
	"<s:complexType mixed=\"true\">" +
	"<s:sequence>" +
	"<s:any />" +
	"</s:sequence>" +
	"</s:complexType>" + 
	"</s:element>" +
	"</s:sequence>" +
	"</s:complexType>" +
	"</s:element>"; 
	
	private  boolean sessionActivated = true;
	private boolean _debug=true;
	
	

	

	public SOAPProxy(SOAPApplication soapApplication) {
		sa = soapApplication;
	}

	/**
	 * @return the aplicationContext
	 */
	public SOAPApplication getAplication() {
		return sa ;
	}
	
	public  String processSoapCall(HttpRequest request,HttpResponse response) throws IOException{


		HashMap parameters =new HashMap();
		parameters.put(SOAPProxy.SOAPAplication, sa);
		SOAPCall soapCall;
		XMLSerializable XMLResponse;
		String SOAPResponse= new String(SOAP_RESPONSE_TEMPLATE);
		try {
			soapCall = parseCall(request, response,parameters);		

			if (soapCall.getID()!=null){
				SOAPResponse=StringFormat.replaceAll(SOAPResponse,"${header}",SOAP_HEADER_RESPONSE_TEMPLATE);
				SOAPResponse=StringFormat.replaceAll(SOAPResponse,"${idsession}",soapCall.getID());
			} else {
				SOAPResponse=StringFormat.replaceAll(SOAPResponse,"${header}","");	
			}
			SOAPResponse=StringFormat.replaceAll(SOAPResponse,"${method}",soapCall.getMethodName());
			SOAPResponse=StringFormat.replaceAll(SOAPResponse,"${className}",soapCall.getClassName());
			XMLResponse=invoke(soapCall,request,parameters);
			if (XMLResponse!=null){
				SOAPResponse=StringFormat.replaceAll(SOAPResponse,"${return}",XMLResponse.serializeToXML());
			}else{
				SOAPResponse=StringFormat.replaceAll(SOAPResponse,"${return}","");
			}
			SOAPResponse=StringFormat.replaceAll(SOAPResponse,"${fault}","");
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			SOAPResponse=StringFormat.replaceAll(SOAPResponse,"${return}","");
			String SOAPFault =new String(SOAP_FAULT_TEMPLATE);
			SOAPFault = StringFormat.replaceAll(SOAPFault,"${faultstring}",e.getCause().getMessage());
			SOAPFault = StringFormat.replaceAll(SOAPFault,"${faultcode}",e.getCause().getStackTrace()[0].toString());
			SOAPFault = StringFormat.replaceAll(SOAPFault,"${faultactor}",e.getCause().getClass().toString());					
			SOAPResponse=StringFormat.replaceAll(SOAPResponse,"${fault}",SOAPFault);
			
			if (_debug) e.printStackTrace();
		}
		if (_debug) System.out.println(SOAPResponse);
		return SOAPResponse;
	}

	
	
	/**
	 * @param sb
	 * @throws SOAPSessionException 
	 */
	private  SOAPCall parseCall(HttpRequest request,HttpResponse response, HashMap parameters) throws Exception {
		InputStream is=request.getInputStream();
		int length=request.getContentLength();	
		DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
		factory.setNamespaceAware(true);
		String methodName=null;
		String className=null;
		String idSession=null;
		String initiate=null;
		String terminate=null;
		String username=null;
		String password=null;
		String timeout=null;
		
		try{
			DocumentBuilder builder=factory.newDocumentBuilder();
			DataInputStream dis = new DataInputStream(is);
			
			StringBuffer sb=new StringBuffer();
			try{
				char a=(char)dis.read();
				sb.append(a);
				while(dis.available()>0) {
					a=(char)dis.read();
					sb.append(a);
				}
			}catch (Exception e){
				System.out.println(e.getLocalizedMessage());
			}
			
			if (_debug) System.out.print(sb.toString());

			ByteArrayInputStream bris = new ByteArrayInputStream(sb.toString().getBytes());
			
			Document document = builder.parse(bris);
			String ns=document.getFirstChild().getNamespaceURI();
			NodeList header=document.getElementsByTagNameNS(ns,"Header");
			try{
				header=header.item(0).getChildNodes();
			} catch(Exception e){
				//no header
				header = null;
			}
			NamedNodeMap attribs=null;
			Node session=null;
			Node auth=null;
			if (header!=null){ 
				for (int i=0;i<header.getLength();i++){
					if (header.item(i).getLocalName()!=null){
					if (header.item(i).getLocalName().equalsIgnoreCase("sqlSession")){
						session=header.item(i);
						String hns=session.getNamespaceURI();
						
						attribs=session.getAttributes();
						if (attribs!=null){ 
							Node tmp=attribs.getNamedItemNS(hns,"sessionId");
							idSession =(tmp!=null)?tmp.getNodeValue():null; 
							tmp=attribs.getNamedItemNS(hns,"initiate");
							initiate =(tmp!=null)?tmp.getNodeValue():null;
							tmp=attribs.getNamedItemNS(hns,"terminate");
							terminate =(tmp!=null)?tmp.getNodeValue():null;
							tmp=attribs.getNamedItemNS(hns,"username");
							username =(tmp!=null)?tmp.getNodeValue():null;
							tmp=attribs.getNamedItemNS(hns,"password");
							password =(tmp!=null)?tmp.getNodeValue():null;
							tmp=attribs.getNamedItemNS(hns,"timeout");
							timeout =(tmp!=null)?tmp.getNodeValue():"120000000";
						}
					}
					if (header.item(i).getLocalName().equalsIgnoreCase("BasicAuth")){
						auth=header.item(i);
						username=auth.getFirstChild().getFirstChild().getNodeValue();
						password=auth.getLastChild().getFirstChild().getNodeValue();
					}
					
					//sessions for basic systems like tcl SOAP 
					if (header.item(i).getLocalName().equalsIgnoreCase("sessionId")){
						idSession=header.item(i).getTextContent();			
					}
					if (header.item(i).getLocalName().equalsIgnoreCase("SSAuth-userName")){
						username=header.item(i).getTextContent();		
					}
					if (header.item(i).getLocalName().equalsIgnoreCase("SSAuth-password")){
						password=header.item(i).getTextContent();		
					}
					if (header.item(i).getLocalName().equalsIgnoreCase("SSAuth-initiateSession")){
						initiate=header.item(i).getTextContent();		
					}
					if (header.item(i).getLocalName().equalsIgnoreCase("SSAuth-terminateSession")){
						terminate=header.item(i).getTextContent();			
					}
					if (header.item(i).getLocalName().equalsIgnoreCase("SSAuth-timeout")){
						timeout=header.item(i).getTextContent();			
					}
					}	
				}
			}
			
			NodeList body=document.getElementsByTagNameNS(ns,"Body");
			NodeList method=body.item(0).getChildNodes();
			int i=0;
			while (method.item(i).getLocalName()==null) i++;
			methodName=method.item(i).getLocalName();
			attribs=method.item(i).getAttributes();
			String ns2=method.item(i).getPrefix();
			className=attribs.getNamedItem("xmlns:"+((ns2==null)?"":ns2)).getNodeValue();
			NodeList parameterNodes=method.item(i).getChildNodes();
			for (i =0;i<parameterNodes.getLength();i++){
				if (parameterNodes.item(i).getLocalName()!=null ){
				if (i<parameterNodes.getLength()){
				String val=parameterNodes.item(i).getFirstChild().getNodeValue();
				parameters.put(parameterNodes.item(i).getLocalName(),val);	
				}}
			}
			//*************************************
			//Session updates if exist
			if (idSession!=null){
			
				SOAPSession ss=(SOAPSession)SOAPSessions.get(idSession);
				if (ss!=null){
					ss.setInitiationTime(System.currentTimeMillis());
				
				}
			}
			//*************************************
			//Session creation
			if ("TRUE".equalsIgnoreCase(initiate)){
				//deprecated idSession=UniqueIdentifier.getID();
				SOAPSession newSession=new SOAPSession();
				
				newSession.setUserName(username);
				newSession.setPassword(password);
				newSession.setTimeout(Integer.parseInt(timeout));
				idSession = UniqueIdentifier.calculatesMD5(newSession.toString());
				newSession.setId(idSession);
				//it does't store the session if any session listener returns false
				if (fireSessionInitiated(newSession,request,parameters))
					SOAPSessions.put(idSession,newSession);
			

			}
			//*************************************
			//Session Finalization
			if ("TRUE".equalsIgnoreCase(terminate)){
				
				SOAPSession oldSession=(SOAPSession) SOAPSessions.get(idSession);
				fireSessionTerminated(oldSession,request,parameters);
				SOAPSessions.remove(idSession);
				
			}
			
			parameters.put(SOAP_SESSION_ID,idSession);
			parameters.put(SOAPProxy.SOAPSession,SOAPSessions.get(idSession));
			
			
		} catch (SAXParseException spe){
			spe.printStackTrace();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return (new SOAPCall(className,methodName,idSession));
	}


	public  String getWsdl(){
		return "";
	}


	public  XMLSerializable invoke(SOAPCall soapCall,HttpRequest request,HashMap parameters) throws Exception {
		
	//get class and method and cache it
		Class c=null;
		Method m=null;
		
		if ((m=(Method)MethodsCache.get(soapCall.getClassName()+"."+soapCall.getMethodName()))==null){
			
			
			try {
				c = Class.forName(soapCall.getClassName());
			} catch (ClassNotFoundException e2) {
				e2.printStackTrace();
			}
			
			
			if (soapCall.getMethodName() != null && c!=null){		
				try {
					m =c.getMethod(soapCall.getMethodName(),new Class[] {
							HashMap.class });
					MethodsCache.put(soapCall.getClassName()+"."+soapCall.getMethodName(),m);
					
					//Only one instance 
					if (MethodsCache.get(soapCall.getClassName())==null)
							MethodsCache.put(soapCall.getClassName(),c.newInstance());
					
				} catch (SecurityException e) {
					e.printStackTrace();
				} catch (NoSuchMethodException e) {
					e.printStackTrace();
				}catch (InstantiationException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}	
			}
		}
		
		fireSessionFilter((SOAPSession)SOAPSessions.get(soapCall.getID()),request,parameters);
		if (m!=null) return (XMLSerializable) m.invoke(MethodsCache.get(soapCall.getClassName()),new Object[]{parameters});						
		return null;
	}

	

	public  Map getSessions(){
		return SOAPSessions;
	}
	
	public  SOAPSession getSession(String id){
		return  (SOAPSession)SOAPSessions.get(id);
	}
	
	public  void terminateSession(SOAPSession session,HttpRequest request,HashMap parameters)throws SOAPSessionException{
		
		fireSessionTerminated(session,request,parameters);
		SOAPSessions.remove(session.getId());
	}


	public  void addSOAPSessionListener(SOAPSessionListener ssl) {
		SOAPSessionListeners.add(ssl);

	}

	public  void removeSOAPSessionListener(SOAPSessionListener ssl) {
		SOAPSessionListeners.remove(ssl);
	}

	boolean fireSessionInitiated(SOAPSession session,HttpRequest request,HashMap parameters) throws SOAPSessionException {
		boolean ok = true;
		if (!SOAPSessionListeners.isEmpty()) {
			for (int i = 0; i < SOAPSessionListeners.size(); i++) {
				SOAPSessionListener ssl =
					(SOAPSessionListener) SOAPSessionListeners.get(i);
				if(!ssl.SOAPSessionInitiated(new SOAPSessionEvent(session,request,parameters)))
					ok=false;
			}
		}
		return ok;

	}

	public  void fireSessionTerminated(SOAPSession session,HttpRequest request,HashMap parameters) throws SOAPSessionException {

		if (!SOAPSessionListeners.isEmpty()) {
			for (int i = 0; i < SOAPSessionListeners.size(); i++) {
				SOAPSessionListener ssl =
					(SOAPSessionListener) SOAPSessionListeners.get(i);
				ssl.SOAPSessionTerminated(new SOAPSessionEvent(session,request,parameters));
			}
		}

	}


	public  void addSOAPSessionFilter(SOAPSessionFilter ssl) {
		SOAPSessionFilters.add(ssl);

	}

	public  void removeSOAPSessionFilter(SOAPSessionFilter ssl) {
		SOAPSessionFilters.remove(ssl);
	}
	
	void fireSessionFilter(SOAPSession session,HttpRequest request,HashMap parameters) throws SOAPSessionException {
		boolean ok = true;
		if (!SOAPSessionFilters.isEmpty()) {
			for (int i = 0; i < SOAPSessionFilters.size(); i++) {
				SOAPSessionFilter ssl =
					(SOAPSessionFilter) SOAPSessionFilters.get(i);
				ssl.doFilter(new SOAPSessionEvent(session,request,parameters));				
			}
		}
	}
	
}
